use crate::model::FudaThing;
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use surrealdb::sql::Thing;
use ts_rs::TS;

use super::trade_holding_data::TradeHoldingData;

///
/// 指定证券的持仓头寸，应该从交易软件同步过来
///
/// TradePosition->trade_position_of->Security
///
#[derive(Debug, Serialize, Deserialize, Default, TS, Clone)]
#[ts(export)]
pub struct TradeHolding {
    /// 唯一标识符，Tpos.security_id e.g. Tpos.SH.STOCK.600519
    #[ts(as = "Option<FudaThing>")]
    pub id: Option<Thing>,

    /// 证券ID，如SH.STOCK.600519 或 MONEY_CNY：MY_HOLDINGS_MONEY_CNY
    pub holding_id: String,

    /// 止损价
    pub stop_price: Option<f64>,
    /// 止盈价
    pub take_profit_price: Option<f64>,

    /// 持仓数据
    #[serde(serialize_with = "serialize_data")]
    pub data: Option<TradeHoldingData>,

    /// 记录更新日期 HLC
    pub last_updated: Option<String>,
}

impl TradeHolding {
    pub fn id_from(holding_id: &str) -> Result<String> {
        Ok(format!("{}", holding_id))
    }

    pub fn id_raw(&self) -> String {
        let id = self.id.clone().unwrap();
        id.id.to_raw()
    }
}

fn serialize_data<S>(data: &Option<TradeHoldingData>, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer
{
    match data {
        Some(dt) => {
            let mut dtmap = serde_json::Map::new();
            dtmap.insert(
                "type".into(),
                serde_json::Value::String(
                    format!("com.fuda.trade.model.TradeHoldingData.{}", dt.tname())
                )
            );
            match dt {
                TradeHoldingData::Money(data) => {
                    let data_map = serde_json
                        ::to_value(data)
                        .map_err(|e| serde::ser::Error::custom(e.to_string()))?;
                    dtmap.extend(data_map.as_object().unwrap().clone());
                }
                TradeHoldingData::Share(data) => {
                    let data_map = serde_json
                        ::to_value(data)
                        .map_err(|e| serde::ser::Error::custom(e.to_string()))?;
                    dtmap.extend(data_map.as_object().unwrap().clone());
                }
            }

            serializer.collect_map(dtmap.iter())
        }
        None => serializer.serialize_none(),
    }
}

/// A wrapper around Vec<TradeHolding> to implement Into<zenoh::value::Value>
#[derive(Debug, Serialize, Deserialize)]
pub struct TradeHoldingList(pub Vec<TradeHolding>);

impl From<Vec<TradeHolding>> for TradeHoldingList {
    fn from(vec: Vec<TradeHolding>) -> Self {
        TradeHoldingList(vec)
    }
}

impl Into<zenoh::value::Value> for TradeHoldingList {
    fn into(self) -> zenoh::value::Value {
        match serde_json::to_vec(&self.0) {
            Ok(bytes) => zenoh::value::Value::from(bytes),
            Err(e) => panic!("Failed to serialize TradeHoldingList to JSON: {}", e),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::model::trade::trade_holding_data::{ self };

    use super::*;
    use log::debug;

    #[tokio::test]
    async fn test_trade_holding_serde() {
        pretty_env_logger::init_timed();
        debug!("debug No more records available; exit the loop");
        println!("print No more records available; exit the loop");

        let holding = TradeHolding {
            id: Some(Thing::from(("trade_holding", "SH.STOCK.600519"))),
            holding_id: "SH.STOCK.600519".to_string(),
            data: Some(
                TradeHoldingData::Money(trade_holding_data::Money {
                    currency: "CNY".to_string(),
                    total: 888.0,
                    available: 666.0,
                    asset_total: 666.0,
                    toady_profit: 888.0,
                    holding_profit: 123.0,
                })
            ),
            ..Default::default()
        };

        let payload = serde_json::to_string(&holding).unwrap();

        println!("payload: {}", payload)
    }
}
